#include "TaskHighlight.h"
#include "SingletonHolder.h"
#include <fstream>
#include <cstring>
#include <cstdio>
#include <iterator>
#include <locale>
#include <sstream>
#include <Tools.h>
#include <boost/lexical_cast.hpp>
#include <boost/program_options/detail/utf8_codecvt_facet.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

#include "utf8.h"




namespace Tasks {

	namespace{
		using namespace std;

		//////////////////////////////////////////////////////////////////////////
		//						HTMLREADER IMPLEMENTATION						//
		//////////////////////////////////////////////////////////////////////////

		HtmlReader::HtmlReader(std::string theFile)
		{
			FILE *file = fopen(theFile.c_str(),"r");
			if(file)
			{
				char *data = NULL;
				unsigned int len=0;

				fseek(file, 0, SEEK_END);
				len=ftell(file);
				fseek(file, 0, SEEK_SET);

				data = new char[len];
				fclose(file);

				htmlParserCtxtPtr parser = htmlCreateMemoryParserCtxt(data,len);
				myHtmlDoc = htmlCtxtReadFile(parser,theFile.c_str(),"UTF-8",HTML_PARSE_NOERROR);
				delete[] data;

				SingletonHolder::Instance().WriteMsg("Opening file for highlighting :" + theFile);
			}
			else
			{
				myHtmlDoc = NULL;
			}
		}

		HtmlReader::~HtmlReader(void)
		{
			if(myHtmlDoc)
				xmlFreeDoc(myHtmlDoc);

		}


		bool HtmlReader::StartParsing()
		{
			if(myHtmlDoc)
			{
				SingletonHolder::Instance().WriteMsg("Starting highlighting");
				walk_tree(xmlDocGetRootElement(myHtmlDoc));
				return true;
			}

			return false;
		}


		bool HtmlReader::Save(std::string theOutput)
		{
			if(myHtmlDoc)
			{
				int res = htmlSaveFileEnc(theOutput.c_str(),myHtmlDoc,"UTF-8");
				if(res > 0)
				{
					postprocess(theOutput);
					return true;
				}
				else
					return false;
			}

			return false;
		}


		void HtmlReader::walk_tree(xmlNode *theNode)
		{
			xmlNode *actualNode = NULL;

			for(actualNode = theNode; actualNode ; actualNode = actualNode->next)
			{
				if(actualNode->content != NULL)
				{
					char *p_res = *OnTextParsed(actualNode->name,actualNode->content);
					if(p_res!=NULL)
					{
						actualNode->content = xmlCharStrdup(p_res);
					}
				}
				walk_tree(actualNode->children);
			}
		}


		void HtmlReader::postprocess(std::string theFile)
		{
			std::wstring _wholepage;
			std::wifstream aFile;

			std::locale _utf8loc(std::locale(),new boost::program_options::detail::utf8_codecvt_facet());
			aFile.imbue(_utf8loc);

			aFile.open(theFile.c_str());
			if(aFile.is_open())
			{

				SingletonHolder::Instance().WriteMsg("---Doing final touchups in the highlighted file!");

				size_t _length = 0; 
				aFile.seekg(0,std::ios::end);
				_length = aFile.tellg();
				aFile.seekg(0,std::ios::beg);

				wchar_t *p_buffer = new wchar_t[_length];
				aFile.read(p_buffer,_length);
				_wholepage.append(p_buffer,_length);

				delete[] p_buffer;
				aFile.close();

				Tools::ReplaceAll<std::wstring>(_wholepage,L"&lt;",L"<");
				Tools::ReplaceAll<std::wstring>(_wholepage,L"&gt;",L">");

				std::wofstream aOutput(theFile.c_str(),std::ios::out);
				if(aOutput.is_open())
				{
					aOutput.write(_wholepage.c_str(),_wholepage.size());
					aOutput.close();
				}
			}
		}


		//////////////////////////////////////////////////////////////////////////
		//					  HIGHLIGHTER IMPLEMENTATION						//
		//////////////////////////////////////////////////////////////////////////

		Highlighter::Highlighter()
		{
			myReader = NULL;
		}


		bool Highlighter::Open(const std::string &theMatchIndexes,const std::string &thePathToHtml)
		{
			string _htmlPage;
			ifstream _streamReader;

			if(myReader)
				delete myReader;

			myReader = new HtmlReader(thePathToHtml);
			if(myReader)
			{
				myReader->OnTextParsed.connect(boost::bind(&Highlighter::parsed_part,this,_1,_2));
				return ConvertIndex(theMatchIndexes);
			}

			return false;
		}

		bool Highlighter::Save(std::string whereTo)
		{
			if(myReader)
			{
				SingletonHolder::Instance().WriteMsg("---Saving the highlighted file");
				myReader->Save(whereTo);
				return true;
			}
			return false;
		}

		void Highlighter::Higlight()
		{
			myLastIndex = 0;
			myIndexCount = 0;
			myStartParse = false;

			if(myReader)
			{
				myReader->StartParsing();
			}
		}


		char* Highlighter::parsed_part(const unsigned char *nodeName, unsigned char *nodeValue)
		{
			if(nodeName == NULL)
				return NULL;

			std::string _nodeName(reinterpret_cast<const char*>(nodeName));
			if(_nodeName == "TITLE" || _nodeName == "title")
				return NULL;

			if(_nodeName != "text")
				return NULL;

			if(nodeValue == NULL || nodeValue[0] <= 13)
				return NULL;

			std::string _valueUTF8(reinterpret_cast<char*>(nodeValue));
			std::wstring _text;
			utf8::utf8to16(_valueUTF8.begin(),_valueUTF8.end(),back_inserter(_text));

			if(_text.size() < 1)
				return NULL;

			if(_text[0] <= 32)
				return NULL;

			bool changeMade = false;
			normalize(_text);
			kill_newlines(_text);
			for(size_t x=0; x < _text.length();)
			{
				for(size_t i=0; i < myIndexes.size(); ++i)
				{
					if(myIndexes[i].first == myIndexCount && myIndexes[i].second == myIndexCount)
					{
						_text.insert(x,highlight_prefix);
						UpdateIndexes(i,highlight_prefix.length());
						myIndexCount += highlight_prefix.length();
						x += highlight_prefix.length();
						_text.insert(x+1,highlight_postfix);
						UpdateIndexes(i,highlight_postfix.length());
						myIndexCount += highlight_postfix.length();
						x += highlight_postfix.length();
						changeMade = true;
						break;
					}
					else
					{
						if( (myIndexCount) == (myIndexes[i].first+ myIndexStart) ) // zaciatok
						{
							_text.insert(x,highlight_prefix);
							UpdateIndexes(i,highlight_prefix.length());
							myIndexCount += highlight_prefix.length();
							x += highlight_prefix.length();
							changeMade = true;
							break;
						}
						else if( (myIndexCount) == (myIndexes[i].second + myIndexStart) ) //koniec
						{
							_text.insert(x+1,highlight_postfix);
							UpdateIndexes(i,highlight_postfix.length());
							myIndexCount += highlight_postfix.length();
							x += highlight_postfix.length();
							changeMade = true;
							break;
						}
					}
				}
				myIndexCount++;
				x++;
			}

			if(changeMade)
			{
				_valueUTF8.clear();
				utf8::utf16to8(_text.begin(),_text.end(),back_inserter(_valueUTF8));
				char* tmp = new char[_valueUTF8.size()+1];
				strncpy(tmp,_valueUTF8.c_str(),_valueUTF8.size());
				tmp[_valueUTF8.size()] = '\0';
				//UpdateIndexes(0,-1);	//treba posunut spat indexi o 1 kvoli novemu riadku 
				return tmp;
			}

			return NULL;
		}


		void Highlighter::normalize(std::wstring &input)
		{
			boost::algorithm::trim_right(input);
			input.append(1,' ');
		}

		void Highlighter::UpdateIndexes(std::size_t from_index, std::size_t theOffset)
		{
			for(size_t i=from_index; i < myIndexes.size(); ++i)
			{
				myIndexes[i].first += theOffset;
				myIndexes[i].second += theOffset;
			}
		}

		void Highlighter::UpdateIndexes(std::size_t from_index, int theOffset)
		{
			for(size_t i=from_index; i < myIndexes.size(); ++i)
			{
				myIndexes[i].first += theOffset;
				myIndexes[i].second += theOffset;
			}
		}


		bool Highlighter::ConvertIndex(const std::string &theMatchIndexes)
		{
			//rozdelime si indexy na jednotlive casti
			vector<string> _result = Tools::Split(const_cast<string&>(theMatchIndexes),";");

			if( _result.size() < 1 )
				return false;

			myIndexStart = 0;
			myLastIndex = 0;
			myIndexCount = 0;

			myIndexes.reserve(_result.size()-1);  //pre allocujeme si miesto pre zvysenie rychlosti

			try
			{
				//uplny prvy index ktory je stale samostatne predstavuje referencny index
				//od ktoreho pocitame zvysok indexov
				myIndexStart = boost::lexical_cast<index>(_result[0]);

				for(size_t i=1; i < _result.size(); i++)
				{
					pair<string,string> _pair = Tools::Separate(_result[i],'-',true);
					index first = boost::lexical_cast<index>(_pair.first);
					index second = boost::lexical_cast<index>(_pair.second);
					/*if(first == second)
					{
					second++;
					}*/
					myIndexes.push_back(make_pair<index,index>(first,second));
				}

				return true;
			}
			catch(...)
			{
				SingletonHolder::Instance().WriteEx("Wrong format of the word indexes");
				return false;
			}
		}

		void Highlighter::kill_newlines(std::wstring &input)
		{
			std::wstring::iterator iter = input.begin();
			bool isLastSpace = false;

			for(; iter != input.end()-1; iter++)
			{
				if((*iter) == ' ')
				{	
					if(isLastSpace)
					{
						input.erase(--iter);
					}	
					isLastSpace = true;
				}
				else
				{
					isLastSpace = false;
				}
			}
		}

	}//end of anonym namespace



TaskHighlight::TaskHighlight(DB::IDatabase *pDbConnection)
{
	my_pDbConnection = pDbConnection;
}

void TaskHighlight::operator ()()
{
	try
	{
		
		//ziskame 2 prijate primarne kluce ktore sa shoduju
		std::vector<int> result_pkeys = myData.theContainer.GetContainer<int>();
		if(result_pkeys.size()!=2)
		{
			SingletonHolder::Instance().WriteEx("Wrong number of table primary keys has been received.");
			SingletonHolder::Instance().WriteMsg("Aborting Task Highlight!");
			return;
		}
		
		std::string _pracaAID = boost::lexical_cast<std::string>(result_pkeys[0]);
		std::string _pracaBID = boost::lexical_cast<std::string>(result_pkeys[1]);

		//ziskame zhody ktore je potrebne vyznacit
		std::string sqlSelect = "SELECT indexes_a,indexes_b FROM zhoda WHERE id_praca_a=" + _pracaAID + " AND id_praca_b=" + _pracaBID;
		std::vector<DB::Datapair> _selectResult;
		my_pDbConnection->Select(sqlSelect,_selectResult);

		if(_selectResult.size() < 1)
		{
			SingletonHolder::Instance().WriteEx("No data for select:" + sqlSelect);
			SingletonHolder::Instance().WriteMsg("Aborting Task Highlight!");
			return;
		}

		DB::DataPairWrapper _dataIndexes(_selectResult);
		std::string _IndexesA = _dataIndexes.GetOne<std::string>("indexes_a");
		std::string _IndexesB = _dataIndexes.GetOne<std::string>("indexes_b");

		sqlSelect = "SELECT praca_html FROM praca_studenta WHERE id_praca=" + _pracaAID;
		_selectResult.clear();
		my_pDbConnection->Select(sqlSelect,_selectResult);

		if(_selectResult.size() < 1)
		{
			SingletonHolder::Instance().WriteEx("No data for select:" + sqlSelect);
			SingletonHolder::Instance().WriteMsg("Aborting Task Highlight!");
			return;
		}

		DB::DataPairWrapper _data(_selectResult);
		std::string _htmlA = _data.GetOne<std::string>("praca_html");

		sqlSelect = "SELECT praca_html FROM praca_studenta WHERE id_praca=" + _pracaBID;
		_selectResult.clear();
		my_pDbConnection->Select(sqlSelect,_selectResult);

		if(_selectResult.size() < 1)
		{
			SingletonHolder::Instance().WriteEx("No data for select:" + sqlSelect);
			SingletonHolder::Instance().WriteMsg("Aborting Task Highlight!");
			return;
		}

		DB::DataPairWrapper _data2(_selectResult);
		std::string _htmlB = _data2.GetOne<std::string>("praca_html");
		
		std::string _htmlAHighLight = Tools::GetPath(_htmlA) + "highlgta.html";
		std::string _htmlBHighLight = Tools::GetPath(_htmlB) + "highlgtb.html";

		if(myHighlighter.Open(_IndexesA,_htmlA))
		{
			myHighlighter.Higlight();
			myHighlighter.Save(_htmlAHighLight);
		}
		else
		{
			SingletonHolder::Instance().WriteEx("Cant open file: "+_htmlA);
		}

		if(myHighlighter.Open(_IndexesB,_htmlB))
		{
			myHighlighter.Higlight();
			myHighlighter.Save(_htmlBHighLight);
		}
		else
		{
			SingletonHolder::Instance().WriteEx("Cant open file: "+_htmlB);
		}

		myData.theConnection->send(boost::asio::buffer(_htmlAHighLight+"|"+_htmlBHighLight));
		myData.theConnection->close();
		delete myData.theConnection;

		SingletonHolder::Instance().WriteMsg("Highlighting was successfull!");
	}
	catch(std::exception &ex)
	{
		SingletonHolder::Instance().WriteEx(ex.what());
	}
}


}//end of namespace Tasks
